/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.binding.action;

import java.util.*;
import javax.swing.*;
import ch.trackedbean.binding.action.MenuPath.MenuPathSegment;

/**
 * Manager for {@link ISelectionAction}s.<br>
 * This manager builds the backend for the {@link LocalPopupManager}.<br>
 * 
 * @author M. Hautle
 */
public class ActionManager {
    /** Map holding the action mappings. The key declares the object type on which the actions were registered. */
    private static final Map<Class<?>, List<ActionEntry<ISelectionAction>>> MAPPING = new HashMap<Class<?>, List<ActionEntry<ISelectionAction>>>();

    /** Map holding the registered actions. The key is the value retrieved by {@link ISelectionAction#getActionID()}. */
    private static final Map<String, ISelectionAction> ACTIONS = new HashMap<String, ISelectionAction>();

    /** Path representing the default group. */
    private static final MenuPath DEFAULT_GROUP = new MenuPath(new MenuPathSegment[0], null, MenuPathBuilder.DEFAULT_ORDER);

    /**
     * Default constructor.
     */
    private ActionManager() {
    }

    /**
     * Returns the action with the given id.<br>
     * The id is the value returned by {@link ISelectionAction#getActionID()}.
     * 
     * @param id The action id
     * @return The action or null
     */
    public synchronized static ISelectionAction getAction(String id) {
        return ACTIONS.get(id);
    }

    /**
     * Registers the given action on the given class type.<br>
     * The action id will be registered so that you can retrieve it using {@link #getAction(String)}.<br>
     * 
     * @param <T> The selection type
     * 
     * @param c The destination class
     * @param action The action to register
     */
    public synchronized static <T> void registerAction(Class<?> c, ISelectionAction<T> action) {
        registerAction(c, DEFAULT_GROUP, Integer.MAX_VALUE, action);
    }

    /**
     * Registers the given action on the given class type.<br>
     * The action id will be registered so that you can retrieve it using {@link #getAction(String)}.<br>
     * 
     * @param <T> The selection type
     * 
     * @param c The destination class
     * @param path The location of the action in the popup
     * @param action The action to register
     */
    public synchronized static <T> void registerAction(Class<?> c, MenuPath path, ISelectionAction<T> action) {
        registerAction(c, path, Integer.MAX_VALUE, action);
    }

    /**
     * Registers the given action on the given class type.<br>
     * The action id will be registered so that you can retrieve it using {@link #getAction(String)}.<br>
     * 
     * @param <T> The selection type
     * 
     * @param c The destination class
     * @param path The location of the action in the popup
     * @param order The order value of the action (used to sort them, lower value come first)
     * @param action The action to register
     */
    public synchronized static <T> void registerAction(Class<?> c, MenuPath path, int order, ISelectionAction<T> action) {
        List<ActionEntry<ISelectionAction>> list = MAPPING.get(c);
        if (list == null)
            MAPPING.put(c, list = new ArrayList<ActionEntry<ISelectionAction>>(1));
        list.add(new ActionEntry<ISelectionAction>(path, order, action));
        ACTIONS.put(action.getActionID(), action);
    }

    /**
     * Populates the given holder according to the passed selection.<br>
     * Before an action is added {@link ISelectionAction#selectionChanged(List)} will be invoked, and the action will be added only if <code>true</code> is
     * returned.
     * 
     * @param <T> The selection type
     * 
     * @param selection The selection
     * @param holder The holder were to add the matching actions
     */
    public synchronized static <T> void addActions(final List<T> selection, final IActionHolder holder) {
        if (selection.isEmpty())
            return;
        final Set<Class> visited = new HashSet<Class>();
        for (Class<?> c = selection.get(0).getClass(); c != null; c = c.getSuperclass()) {
            appendActions(selection, c, holder);
            appendInterfaceActions(selection, c, holder, visited);
        }
    }

    /**
     * Adds all actions of the given class (not recursive) to the passed holder.<br>
     * If a found action return <code>false</code> when {@link ISelectionAction#selectionChanged(List)} gets invoked, then it will not be added to the menu.
     * 
     * @param <T> The selection type
     * 
     * @param selection The selected object
     * @param c The concrete class
     * @param holder The holder were to add the actions
     */
    @SuppressWarnings("unchecked")
    private static <T> void appendActions(List<T> selection, Class<?> c, IActionHolder holder) {
        if (!isTypeValideForAllElements(selection, c))
            return;
        final List<ActionEntry<ISelectionAction>> list = MAPPING.get(c);
        if (list == null)
            return;
        for (ActionEntry<ISelectionAction> a : list) {
            if (a.getAction().selectionChanged(selection))
                holder.addAction(a);
        }
    }

    /**
     * Adds all actions for the interfaces implemented by the given class.<br>
     * This method is recursive so it processes every implemented interface of the passed class.
     * 
     * @param selection The selection
     * @param c The class to analyze
     * @param holder The holder were to add the actions
     * @param visited The already visited interfaces
     */
    private static void appendInterfaceActions(List<?> selection, Class<?> c, IActionHolder holder, Set<Class> visited) {
        for (Class<?> i : c.getInterfaces()) {
            // skip already processed interfaces
            if (!visited.add(i))
                continue;
            appendActions(selection, i, holder);
            appendInterfaceActions(selection, i, holder, visited);
        }
    }

    /**
     * Checks if all elements of the selection list were assignable from the given type.
     * 
     * @param selection The selection list
     * @param type The type to check
     * @return True if all elements of the given list were assignable from the given type
     */
    private static boolean isTypeValideForAllElements(List<?> selection, Class<?> type) {
        for (Object o : selection) {
            if (!type.isAssignableFrom(o.getClass()))
                return false;
        }
        return true;
    }

    /**
     * Interface for objects holding {@link ActionEntry}s.
     * 
     * @param <T> The action type
     * @author M. Hautle
     */
    public interface IActionHolder<T extends Action> {
        /**
         * Adds the given action.
         * 
         * @param a The action
         */
        void addAction(ActionEntry<T> a);
    }
}
